import java.util.ArrayList;

/**
 * Node.java Represents a node in a 3d CAN system. The starting node receives a
 * query from the query runner. The node adds itself to the traceroute of the
 * query. If the destination point embedded in the query exists in this node's
 * zone, the call is returned. Otherwise, the distance to the destination is
 * calculated from 6 of this node's neighbors and the query is forwarded to the
 * node that has shortest distance towards the destination.
 * 
 * @author Praneeth Pulusani
 * 
 */
public class Node {
	// Identifiers/coordinates of the node
	private double cmX;
	private double cmY;
	private double cmZ;
	private String nodeID;

	// exclusive bounds - defines the node's zone
	// lower bound in x dimension
	private double xL;
	// upper bound in x dimension
	private double xU;
	// lower bound in y dimension
	private double yL;
	// upper bound in y dimension
	private double yU;
	// lower bound in z dimension
	private double zL;
	// upper bound in z dimension
	private double zU;

	// the CAN this node belongs to
	private CAN can;

	/**
	 * Constructor - creates the node object with the given identity
	 * 
	 * @param cmX
	 *            - center of mass x
	 * @param cmY
	 *            - center of mass y
	 * @param cmZ
	 *            - center of mass z
	 * @param can
	 *            - reference to can of this node
	 */
	public Node(double cmX, double cmY, double cmZ, CAN can) {
		this.cmX = cmX;
		this.cmY = cmY;
		this.cmZ = cmZ;
		this.xL = cmX - 0.5;
		this.xU = cmX + 0.5;
		this.yL = cmY - 0.5;
		this.yU = cmY + 0.5;
		this.zL = cmZ - 0.5;
		this.zU = cmZ + 0.5;
		this.can = can;
		this.nodeID = "" + cmX + cmY + cmZ;

	}

	/**
	 * equals - checks if two nodes are equal by comparing their center of mass
	 * 
	 * @param n
	 *            - the node to compare
	 * @return true if equal false if inequal
	 */
	public boolean equals(Node n) {
		if (this.cmX == n.cmX && this.cmY == n.cmY && this.cmZ == n.cmZ) {
			return true;
		}
		return false;
	}

	/**
	 * hashCode Defined a custom hashcode to use Nodes as keys
	 */
	public int hashCode() {
		return ("" + cmX + cmY + cmZ).hashCode();
	}

	/**
	 * getX
	 * 
	 * @return x coordinate of this node in the can system
	 */
	public double getX() {
		return cmX;
	}

	/**
	 * getY
	 * 
	 * @return Y coordinate of this node in the can system
	 */
	public double getY() {
		return cmY;
	}

	/**
	 * getZ
	 * 
	 * @return Z coordinate of this node in the can system
	 */
	public double getZ() {
		return cmZ;
	}

	/**
	 * zoneContainsPoint Tests if the the given point lies within the bounds of
	 * this node zone
	 * 
	 * @param p
	 * @return true if zone contains the point, false otherwise
	 */
	public boolean zoneContainsPoint(Point p) {
		if (xL < p.getX() && xU > p.getX() && yL < p.getY() && yU > p.getY()
				&& zL < p.getZ() && zU > p.getZ()) {
			return true;
		}
		return false;
	}

	/**
	 * distanceToPoint Calculate and return the distance from center of this
	 * node zone to destination point
	 * 
	 * @param p
	 * @return euclidean distance to given point/coordinates
	 */
	public double distanceToPoint(Point p) {
		return Math.sqrt(Math.pow(
				Math.min(Math.abs(cmX - p.getX()),
						can.getWidth() - Math.abs(cmX - p.getX())), 2)
				+ Math.pow(
						Math.min(Math.abs(cmY - p.getY()), can.getHeight()
								- Math.abs(cmY - p.getY())), 2)
				+ Math.pow(
						Math.min(Math.abs(cmZ - p.getZ()), can.getDepth()
								- Math.abs(cmZ - p.getZ())), 2));
	}

	/**
	 * forward query Get 6 neighbors of this node from can and after checking
	 * the distance between each neighbor to the destination point, forward the
	 * query to the node with shortest distance. As nodes forward queries, the
	 * query gets closer and closer to destination
	 * 
	 * @param q
	 */
	public void forwardQuery(Query q) {
		ArrayList<Node> neighbors = can.getNeighbors(this);
		double shortestDistance = neighbors.get(0).distanceToPoint(
				q.getDestinationPoint());
		Node bestNeighborNode = neighbors.get(0);
		for (int n = 1; n < neighbors.size(); n++) {
			Node curNode = neighbors.get(n);
			if (curNode.distanceToPoint(q.getDestinationPoint()) < shortestDistance) {
				shortestDistance = curNode.distanceToPoint(q
						.getDestinationPoint());
				bestNeighborNode = curNode;
			}

		}
		bestNeighborNode.executeQuery(q);
	}

	/**
	 * executeQuery Add this node to the traceroute of query Then, if the
	 * destination point embedded in the query is within this node, return
	 * otherwise use the forward the query to a neighbor closer the point by
	 * using forwardquery function
	 * 
	 * @param q
	 */
	public void executeQuery(Query q) {
		q.addNodeToHistory(this);
		if (zoneContainsPoint(q.getDestinationPoint())) {
			return;
		} else {
			forwardQuery(q);
		}
	}

	/**
	 * toString Prints the node info in a nice format
	 */
	public String toString() {
		return "X: " + this.cmX + "\tY: " + this.cmY + "\tZ: " + this.cmZ
				+ "\n";
	}

}
